﻿using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml.Linq;

namespace WindowsMobileWebService
{
    /// <summary>
    /// Summary description for Service
    /// </summary>
    [WebService(Namespace = "http://blog.wmdev.net/windowsmobilewebservice")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [ToolboxItem(false)]
    // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 
    // [System.Web.Script.Services.ScriptService]
    public class Service : System.Web.Services.WebService
    {
        #region Declarations
        private const string USER_AGENT = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; WOW64; SLCC1; .NET CLR 2.0.50727; InfoPath.2; Media Center PC 5.0; .NET CLR 3.0.04506; .NET CLR 3.5.21022)";
        #endregion

        #region Person Lookup
        [WebMethod]
        public string PersonLookup(string who, string where)
        {
            // Get search result page
            string url = string.Format("http://www.whitepages.com/search/FindPerson?who={0}&where={1}", who, where);
            HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
            StreamReader responseReader = new StreamReader(request.GetResponse().GetResponseStream());
            string responseData = responseReader.ReadToEnd();
            responseReader.Close();

            // More than one?
            int i = 0;
            if((i = responseData.IndexOf("results_multiple_widget_matching")) > -1)
            {
                i = responseData.IndexOf("<strong>", i) + 8;
                string matches = responseData.Substring(i, responseData.IndexOf("\n", i) - i);
                throw new Exception(string.Format("{0} matches, refine search!", matches));
            }

            // Any
            if(responseData.IndexOf("class=\"fn n\">") < 0)
                throw new Exception("No matches, try again!");

            // Only one
            string s = string.Format("{0}\n{1}\n{2}, {3} {4}\n{5}",
                extractPersonValue(responseData, "fn n"), // name
                extractPersonValue(responseData, "street-address"), // street
                extractPersonValue(responseData, "locality"), // city
                extractPersonValue(responseData, "region"), // state
                extractPersonValue(responseData, "postal-code"), // zip
                extractPersonValue(responseData, "tel")); // phone

            return s;
        }

        private string extractPersonValue(string s, string name)
        {
            string valueDelimiter = "class=\"" + name + "\">";

            int valuePosition = s.IndexOf(valueDelimiter);
            if(valuePosition < 0)
                return string.Empty;
            int startPosition = valuePosition + valueDelimiter.Length;
            int endPosition = s.IndexOf("<", startPosition);

            return s.Substring(startPosition, endPosition - startPosition);
        }
        #endregion

        #region Flight Lookup
        [WebMethod]
        public string FlightLookup(string flight)
        {
            // Get session cookie and viewstate
            CookieContainer cookies = new CookieContainer();
            string url = "http://travel.flightexplorer.com/TrackFlight.aspx";
            HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
            request.UserAgent = USER_AGENT;
            request.CookieContainer = cookies;
            StreamReader responseReader = new StreamReader(request.GetResponse().GetResponseStream());
            string responseData = responseReader.ReadToEnd();
            responseReader.Close();

            // Extract the viewstate value and build out POST data
            string viewState = extractValue(responseData, "__VIEWSTATE");
            string postData = String.Format("__VIEWSTATE={0}&leftcol1%3Aemail=Newsletter+Sign-Up+Email&leftcol1%3AhomeAID=&leftcol1%3AcurrHomeairportDiv=homeairportDiv&FastTrack1%3AflightNum={1}&FastTrack1%3AsubmitBttn=Track+Flight&FastTrack1%3ActrlState=track&FastTrack1%3AnumUpdate=&FastTrack1%3AFTImgConfig=&scrollVal=&state=",
                viewState, flight);

            // Now post to the same page
            request = WebRequest.Create(url) as HttpWebRequest;
            request.UserAgent = USER_AGENT;
            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";
            request.CookieContainer = cookies;
            StreamWriter requestWriter = new StreamWriter(request.GetRequestStream());
            requestWriter.Write(postData);
            requestWriter.Close();
            responseReader = new StreamReader(request.GetResponse().GetResponseStream());
            responseData = responseReader.ReadToEnd();
            responseReader.Close();

            // Get flight status
            int i = 0;
            string status = null;
            string result = "Flight not found";
            if(responseData.IndexOf(result) < 0)
            {
                string s = "Status:";
                if((i = responseData.IndexOf(">" + s + "<")) > -1)
                {
                    i = responseData.IndexOf("class='ft1'>", i) + 12;
                    status = responseData.Substring(i, responseData.IndexOf("<", i) - i);
                    result = string.Format("{0} {1}", s, status);
                    switch(status)
                    {
                        case "Planned":
                            s = "Departure in:";
                            i = responseData.IndexOf(">" + s + "<", i);
                            i = responseData.IndexOf("FTText2' colspan=3>", i) + 19;
                            result += string.Format("\n{0} {1}", s, responseData.Substring(i, responseData.IndexOf("<", i) - i));
                            break;
                        case "In Flight":
                            s = "Time remaining:";
                            i = responseData.IndexOf(">" + s + "<", i);
                            i = responseData.IndexOf("class='ft1'>", i) + 12;
                            result += string.Format("\n{0} {1}", s, responseData.Substring(i, responseData.IndexOf("<", i) - i));
                            break;
                    }
                }
            }
            return result;
        }

        private string extractValue(string s, string nameDelimiter)
        {
            string valueDelimiter = "value=\"";

            int namePosition = s.IndexOf(nameDelimiter);
            int valuePosition = s.IndexOf(valueDelimiter, namePosition);
            if(namePosition < 0 || valuePosition < 0)
                return string.Empty;
            int startPosition = valuePosition + valueDelimiter.Length;
            int endPosition = s.IndexOf("\"", startPosition);

            return HttpUtility.UrlEncode(s.Substring(startPosition, endPosition - startPosition));
        }
        #endregion

        #region Package Lookup
        [WebMethod]
        public string PackageLookup(string packageNo)
        {
            // Get DHL tracking page
            string url = string.Format("http://www.dhl.com/cgi-bin/tracking.pl?AWB={0}", packageNo);
            HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
            StreamReader responseReader = new StreamReader(request.GetResponse().GetResponseStream());
            string responseData = responseReader.ReadToEnd();
            responseReader.Close();

            Regex regex = new Regex("<a\\s*href=\\x23[0-9]*?><font(.|\\n)*?>(?<number>.*\\n?.*)</font(.|\\n)*?<a\\shref=\\\"" +
                "(.|\\n)*?\\\">(?<origin>.*\\n?.*)</a(.|\\n)*?<a\\shref=\\\"(.|\\n)*?\\\">(?<destination>." +
                "*\\n?.*)</a(.|\\n)*?face=\\\"arial\\\">(?<status>.*\\n?.*)<img*", RegexOptions.IgnoreCase);

            // Extract using regex
            string s = null;
            Match match = regex.Match(responseData);
            if(match.Success)
            {
                //match.Groups["number"];
                s = string.Format("Origin: {0}\nDest.: {1}\nStatus: {2}",
                    match.Groups["origin"], match.Groups["destination"],
                    match.Groups["status"].ToString().Replace("<BR>", "\n"));
            }
            else
                throw new Exception("Not found, try again!");
            
            return s;
        }
        #endregion

        #region Movie Lookup
[WebMethod]
public Movie[] MovieLookup(string movieTitle)
{
    string url = string.Format("http://www.imdb.com/find?s=all&q={0}", movieTitle);
    HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
    StreamReader responseReader = new StreamReader(request.GetResponse().GetResponseStream());
    string responseData = responseReader.ReadToEnd();
    responseReader.Close();

    int i = responseData.IndexOf("<b>Popular Titles</b>");
    if(i <= 0)
        return new Movie[] { };
    int j = responseData.IndexOf("</p>", i);

    string table = Regex.Match(responseData.Substring(i, j - i), "<table.*?>(.*?)</table>").ToString();

    var q = from Match match in Regex.Matches(table, "<a\\shref=[\"\"\"\"'](?<url>.*?)[\"\"\"\"'].*?>(?<title>.*?)</a>")
            where !match.Groups["title"].ToString().Contains("<img")
            select new Movie { ID = Regex.Match(match.Groups["url"].ToString(), "(?<=\\w\\w)\\d\\d\\d\\d\\d\\d\\d").ToString(),
                Title = HttpUtility.HtmlDecode(match.Groups["title"].ToString()) };

    Movie[] movies = q.ToArray<Movie>();

    foreach(Movie movie in movies)
    {
        url = string.Format("http://www.imdb.com/title/tt{0}", movie.ID.ToString());
        request = WebRequest.Create(url) as HttpWebRequest;
        responseReader = new StreamReader(request.GetResponse().GetResponseStream());
        responseData = responseReader.ReadToEnd();
        responseReader.Close();

        string title = Regex.Match(responseData, "(?<=<(title)>).*(?=<\\/\\1>)").ToString();
        movie.Title = HttpUtility.HtmlDecode(Regex.Match(title, ".*(?=\\s\\(\\d+.*?\\))").ToString()).Replace("\"", string.Empty);
        movie.Year = Regex.Match(title, "(?<=\\()\\d+(?=.*\\))").ToString();
        movie.ImageUrl = Regex.Match(Regex.Match(responseData, "(?<=\\b(name=\"poster\")).*\\b[</a>]\\b").ToString(),
            "(?<=\\b(src=)).*\\b(?=[</a>])").ToString().Replace("\"", string.Empty).Replace("/></", string.Empty);

        try
        {
            if(movie.Title.Contains("(VG)"))
            {
                i = responseData.IndexOf("<h5>Plot Summary:</h5>") > 0 ? responseData.IndexOf("<h5>Plot Summary:</h5>") :
                    responseData.IndexOf("<h5>Tagline:</h5>");
                if(i > 0) j = responseData.IndexOf("</div>", i);
            }
            else
            {
                i = responseData.IndexOf("<h5>Plot:</h5>") > 0 ? responseData.IndexOf("<h5>Plot:</h5>") :
                    responseData.IndexOf("<h5>Plot Summary:</h5>");
                if(i <= 0) i = responseData.IndexOf("<h5>Plot Synopsis:</h5>");
                if(i > 0) j = responseData.IndexOf("<a class=", i);
                if(j <= 0)
                    j = responseData.IndexOf("</div>", i);
            }
            string plotOutline = responseData.Substring(i, j - i).Remove(0, "<h5>Plot:</h5> ".Length);
            plotOutline = HttpUtility.HtmlDecode(plotOutline);
            movie.Description = Regex.Replace(plotOutline.Contains("is empty") || plotOutline.Contains("View full synopsis")
                ? string.Empty : plotOutline, "<a.*?href=[\"'](?<url>.*?)[\"'].*?>(?<name>.*?)</a>", string.Empty);
        }
        catch
        {
            movie.Description = string.Empty;
        }
    }
    return movies;
}
        #endregion

        #region Dictionary Lookup
        [WebMethod]
        public string DictionaryLookup(string word)
        {
            const string host = "www.dict.org";
            const int port = 2628;
            const string database = "wn";

            // Connect to dictionary (RFC 2229) server
            TcpClient tc = new TcpClient(host, port);
            StreamReader sr = new StreamReader(tc.GetStream());
            DictServerStatus status = new DictServerStatus(sr.ReadLine());
            if(status.Code != (int)DictServerStatusCodes.Banner)
                throw new Exception(status.ToString());

            // Send command (DEFINE)
            string command = string.Format("DEFINE \"{0}\" \"{1}\"\r\n", database, word);
            Encoding enc = System.Text.Encoding.ASCII;
            tc.GetStream().Write(enc.GetBytes(command), 0, enc.GetBytes(command).Length);

            // Get definition(s) for word
            status = new DictServerStatus(sr.ReadLine());
            if(status.Code != (int)DictServerStatusCodes.Definitions)
                throw new Exception(status.ToString());

            // Parse definitions
            int definitionCount = Convert.ToInt32(status.Message.Substring(0, status.Message.IndexOf(" ") + 1));
		    Hashtable definitions = new Hashtable(definitionCount);
		    for(int i = 0; i < definitionCount; i++)
            {
                status = new DictServerStatus(sr.ReadLine());
                if(status.Code == (int)DictServerStatusCodes.Definition)
                {
		            string responseText = string.Empty;
                    string s = sr.ReadLine();
		            while(s != ".")
                    {
			            responseText += s + "\r\n";
                        s = sr.ReadLine();
                    }
				    Match definitionLine = Regex.Match(status.Message, "^\\\"(?<word>[^\\\"]+)\\\"\\s+(?<database>[\\S]+)\\s+\\\"(?<dbname>[^\\\"]+)\\\"$");
				    definitions.Add(definitionLine.Groups["database"].ToString(), responseText);
			    }
                else
				    throw new Exception(status.ToString());
		    }
            string definition = string.Empty;
            status = new DictServerStatus(sr.ReadLine());
            if(status.Code == (int)DictServerStatusCodes.Ok)
			    definition = definitions[database].ToString();
		    else
			    throw new Exception(status.ToString());

            return definition;
        }
        #endregion
    }

    #region Movie classes
    public class Movie
    {
        public string ID { get; set; }
        public string Title { get; set; }
        public string Year { get; set; }
        public string ImageUrl { get; set; }
        public string Description { get; set; }
    }
    #endregion

    #region Dictionary classes (and enums)
    public class DictServerStatus
    {
        public readonly int Type;
        public readonly int Function;
        public readonly int Code;
        public readonly string Message;
        private string status;

        public DictServerStatus(string response)
        {
            // All responses must begin with a 3-digit code
            if (!Regex.IsMatch(response, "^\\d{3}\\s+"))
                throw new Exception("Improper status response: " + response);

            this.status = response;
            this.Type = int.Parse(status.Substring(0, 1));
            this.Function = int.Parse(status.Substring(1, 1));
            this.Code = int.Parse(status.Substring(0, 3));
            this.Message = response.Substring(4);
        }

        public bool Ok
        {
            get { return (this.Type < 4); }
        }

        public override string ToString()
        {
            return this.status;
        }
    }

    public enum DictServerStatusCodes : int
    {
        Databases = 110,
        Strategies = 111,
        DatabaseInformation = 112,
        Help = 113,
        ServerInformation = 114,
        Challenge = 130,
        Definitions = 150,
        Definition = 151,
        Matches = 152,
        OptionalInformation = 210,
        Banner = 220,
        ClosingConnection = 221,
        Authenticated = 230,
        Ok = 250,
        SendResponse = 330,
        ServerUnavailable = 420,
        ServerShuttingDown = 421,
        CommandNotRecognized = 500,
        IllegalParameters = 501,
        CommandNotImplemented = 502,
        ParameterNotImplemented = 503,
        AccessDenied = 530,
        AccessDeniedShowInfo = 531,
        UnknownMechanism = 532,
        InvalidDatabase = 550,
        InvalidStrategy = 551,
        NoMatch = 552,
        NoDatabases = 554,
        NoStrategies = 555
    }
    #endregion
}
